// import { redirectTo } from '@tarojs/taro';
import { useCallback, useState, useEffect } from 'react';

export type RequestApi<A extends any[], T> = (...args: A) => Promise<T>

const noop = () => { }

type RequestInternalState<D> = {
  response: D | undefined,
  loading: boolean,
  error: Error | undefined,
  cancel: () => void,
  firstRequestSend: boolean,
}

const initialState: RequestInternalState<any> = {
  response: undefined,
  loading: false,
  error: undefined,
  cancel: noop,
  firstRequestSend: false,
}

if (Object.freeze) Object.freeze(initialState);

/**
 * This hook used for network request status
 *
 * @param requestMethod network request method
 */
export function useRequest<A extends any[], D>(requestMethod: RequestApi<A, D> | undefined | null, that?: any): {
  response: D | undefined;
  setResponse: React.Dispatch<React.SetStateAction<D | undefined>>;
  loading: boolean;
  error: any | undefined;
  cancel: () => void;
  send: (...args: A) => () => void;
  reset: () => void,
  firstRequestSend: boolean;
} {
  const [state, setState] = useState<RequestInternalState<D>>(initialState)
  const setResponse = useCallback<React.Dispatch<React.SetStateAction<D | undefined>>>((value) => {
    setState((prevState) => {
      const prev = prevState.response
      let next: typeof prev
      if (typeof value === 'function') {
        next = (value as ((prevState: D | undefined) => D | undefined))(prev)
      } else {
        next = value
      }
      return { ...prevState, response: next }
    })
  }, [])

  useEffect(() => {
    setState((prevState) => ({
      ...prevState,
      firstRequestSend: false,
    }))
  }, [requestMethod, that])

  const reset = useCallback(() => {
    setState((prevState) => {
      prevState.cancel()
      return {
        ...prevState,
        firstRequestSend: false,
        error: undefined,
        response: undefined,
      }
    })
  }, [])

  const doRequest = useCallback((...args: A) => {
    let didCancel = false
    const cancelFn = () => {
      didCancel = true
    }

    setState((prevState) => ({
      ...prevState,
      loading: true,
      error: undefined,
      cancel: cancelFn,
      firstRequestSend: true,
    }))

    const requestPromise = requestMethod ?
      requestMethod.apply(that, args) :
      Promise.reject(new Error('No request method available.'))

    requestPromise.then((data) => {
      if (data && data.status === 500) {
        // redirectTo({
        //   url: '/error/server'
        // })
      }
      if (!didCancel) {
        setState((prevState) => ({
          ...prevState,
          loading: false,
          ...(((data?.code || data?.status) === 200) || (data?.code === 0) ? {
            response: data.data,
          } : { error: data })
        }))
      }
    }).catch((error) => {
      if (!didCancel) {
        setState((prevState) => ({
          ...prevState,
          loading: false,
          error: error,
        }))
      }
    })

    return cancelFn
  }, [requestMethod, that])

  return {
    response: state.response,
    setResponse: setResponse,
    loading: state.loading,
    error: state.error,
    cancel: state.cancel,
    send: doRequest,
    firstRequestSend: state.firstRequestSend,
    reset: reset,
  }
}
